はじめに
Cloudflareがついにコンテナ実行環境を提供しはじめたので試す。単に動かすだけなら公式のテンプレや既存の記事を参照すればよい。
https://developers.cloudflare.com/containers/get-started/
https://zenn.dev/774u64/articles/1e351b11f21928
それだけでは面白くないので、本記事では私がメンテナンスしているラスタータイルサーバー「chiitiler」のコンテナイメージを動かすことを目標にトライしてみる。 https://github.com/Kanahiro/chiitiler
chiitilerの運用について
https://spatialty-io.github.io/chiitiler-demo/
chiitiler
はデモサイトを公開していて、AWS Lambda + Lambda Web Adapterで動作している。もちろんAmazon ECSなどの「一般的な」コンテナ環境でもよく動作するが、chiitilerはAWS Lambda「でも」よく動くようにチューニングされていたりする。すなわち揮発性のインスタンスでいかにパフォーマンスを落とさないか、ということ。Cloudflare ContainersがAWS Lambdaくらいナイスなコンテナ環境なら嬉しい。
想定手順
ドキュメントや記事を斜め読みした状態での想定手順
- Cloudflareのコンテナレジストリにコンテナイメージをデプロイする
- Cloudflare WorkersからBindings経由でアクセスする
コンテナイメージのデプロイ
冒頭のドキュメントや記事では、Dockerfileを参照する方法が紹介されているが、ビルド済みイメージをデプロイ出来た方が楽ちん。ドキュメントを読むと、もちろんその方法もあった。 https://developers.cloudflare.com/containers/image-management/
# chiitilerのコンテナイメージはghcr.ioで公開しているので、それをpull->pushする
docker pull <public-image>
docker tag <public-image> <image>:<tag>
wrangler containers push <image>:<tag>
なお現状はamd64
アーキテクチャのイメージのみサポートしている模様。ゆえに公開済みイメージからlinux/amd64
のものを選択してpull、雑にタグをつけてpush。
registry.cloudflare.com/<uuid>/chiitiler:amdlatest
というURIとなった。
デプロイしたコンテナイメージを利用したWorkersを実装する
まずはGetting Startedで利用しているテンプレを眺めてみる。Goで書かれたREST-APIをDockerfileで固めてデプロイする形式。コンテナイメージの方はあんまり細かいことは考えなくてよさそう。Workersの部分が本質。
import { Container, getContainer, getRandom } from "@cloudflare/containers";
import { Hono } from "hono";
export class MyContainer extends Container<Env> {
// Port the container listens on (default: 8080)
defaultPort = 8080;
// Time before container sleeps due to inactivity (default: 30s)
sleepAfter = "2m";
// Environment variables passed to the container
envVars = {
MESSAGE: "I was passed in via the container class!",
};
// Optional lifecycle hooks
override onStart() {
console.log("Container successfully started");
}
override onStop() {
console.log("Container successfully shut down");
}
override onError(error: unknown) {
console.log("Container error:", error);
}
}
// Create Hono app with proper typing for Cloudflare Workers
const app = new Hono<{
Bindings: Env;
}>();
// Home route with available endpoints
app.get("/", (c) => {
return c.text(
"Available endpoints:\n" +
"GET /container/<ID> - Start a container for each ID with a 2m timeout\n" +
"GET /lb - Load balance requests over multiple containers\n" +
"GET /error - Start a container that errors (demonstrates error handling)\n" +
"GET /singleton - Get a single specific container instance",
);
});
// Route requests to a specific container using the container ID
app.get("/container/:id", async (c) => {
const id = c.req.param("id");
const containerId = c.env.MY_CONTAINER.idFromName(`/container/${id}`);
const container = c.env.MY_CONTAINER.get(containerId);
return await container.fetch(c.req.raw);
});
// Demonstrate error handling - this route forces a panic in the container
app.get("/error", async (c) => {
const container = getContainer(c.env.MY_CONTAINER, "error-test");
return await container.fetch(c.req.raw);
});
// Load balance requests across multiple containers
app.get("/lb", async (c) => {
const container = await getRandom(c.env.MY_CONTAINER, 3);
return await container.fetch(c.req.raw);
});
// Get a single container instance (singleton pattern)
app.get("/singleton", async (c) => {
const container = getContainer(c.env.MY_CONTAINER);
return await container.fetch(c.req.raw);
});
export default app;
ポイントはふたつで、Container
クラスの定義と、Workersからコンテナを呼び出す方法。
Containerクラスの定義
Workersのコード上に、そのコンテナをどう起動するのか直接書いてしまう仕様。ここではMyContainer
という名前でexportしていて、この文字列をwranglare.jsonc
に設定するという寸法。chiitilerはデフォルトで利用するポートは3000なので、そうしてやる。あと環境変数をいい感じに。
Workersのアプリケーションコード
サンプルコードには、コンテナを触るためのいくつかの方法が示されていて、idFromName()
とgetContainer()
とgetRandom()
がある。今回はインスタンスを複数建ててみたいので、いい感じにロードバランシングしてほしい。ただし…
Currently, routing requests to one of many interchangeable Container instances is accomplished with the `getRandom` helper.
This is temporary — we plan to add native support for latency-aware autoscaling and load balancing in the coming months.
https://developers.cloudflare.com/containers/get-started/
「いい感じのロードバランシング」は鋭意開発中という感じで、現状でそういうことがしたければgetRandom()
を用いることになる模様。ということはつまり、並列稼働してる各インスタンスとコミュニケーションする方法があるということで、これはAWS LambdaやAmazon ECSではちょっと経験したことがない。
もう一点、サンプルコードはHono
を用いてリクエストのパスごとに異なる処理が実装されているが、chiitiler
コンテナはそれ自体がいくつかのエンドポイントを実装していて、各ルートをHono
アプリケーションに実装するのはナンセンスである。なのでfetch
を直接利用してやる。最終的なアプリケーションコードは下記のとおり。
import { Container, getRandom } from '@cloudflare/containers';
export class MyContainer extends Container<Env> {
// Port the container listens on (default: 8080)
defaultPort = 3000;
// Time before container sleeps due to inactivity (default: 30s)
sleepAfter = '2m';
// Environment variables passed to the container
envVars = {
CHIITILER_DEBUG: 'true',
CHIITILER_CACHE_METHOD: 'file',
};
// Optional lifecycle hooks
override onStart() {
console.log('Container successfully started');
}
override onStop() {
console.log('Container successfully shut down');
}
override onError(error: unknown) {
console.log('Container error:', error);
}
}
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext,
): Promise<Response> {
return (await getRandom(env.MY_CONTAINER, 2)).fetch(request);
},
};
wrangler.jsonc
最後にwrangler.jsonc
のようす。
"containers": [
{
"class_name": "MyContainer", // exportしたContainerクラスの名前
"instance_type": "standard",
"image": "registry.cloudflare.com/<uuid>/chiitiler:amdlatest",
"max_instances": 10
}
],
デプロイする
npx wrangler deploy
これによりWorkersがデプロイされる。chiitiler
のデバッグページ(/debug
)を開いてみる。
/debug
ページで地図画像がレンダリングされた、勝利。パフォーマンスも想像よりは悪くないけど良くはない、0.5vCPUしか使えないのがかなり影響していそう。
感想
Workersらしくユニークな操作・実装でコンテナイメージを起動することが出来た。デプロイまでの手軽さはさすがCloudflare。ではこれを使っていくのかというと、まずは料金を見てみる。
Memory | CPU | Disk | |
---|---|---|---|
Free | N/A | N/A | |
Workers Paid | 25 GiB-hours/month included +$0.0000025 per additional GiB-second | 375 vCPU-minutes/month + $0.000020 per additional vCPU-second | 200 GB-hours/month +$0.00000007 per additional GB-second |
https://developers.cloudflare.com/containers/pricing/ |
現在はインスタンスタイプは3種類から選べて、一番リッチなstandard
は4GB RAM、0.5vCPU、4GB Disk(メモリの割にCPUが弱すぎる…)。一番高そうなMemoryで計算してみると、standard
を1インスタンス起動させ続けると、4000JPY/monthとなる(はず、計算が正しければ)。さてこれが安いかというと、そこまで安くないかなと思った。ただCloudflareはエグレス無料という強みがあるので、目的によっては安く済む可能性はある。有望なユースケースは、Workersベースのアプリケーションで、workerdでは出来ないことをしたいときに使うコンテナ環境、かなと思う。
chiitiler
を動かす環境としてはまだ厳しいけども、思っていたよりは普通に動作したので、今後の進化に期待したいところ。